/*
 * batch_main.cpp
 *
 *  Created on: Feb 28, 2011
 *      Author: tqlong
 */

#include <iostream>
#include <boost/program_options.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <armadillo>

class ArmaVec : public arma::vec
{
public:
  ArmaVec(int d = 1) : arma::vec(d) {}
  ArmaVec& operator=(const ArmaVec&v) { arma::vec::operator=(v); return *this; }
  double get(int i) const { return at(i); }
  double& ref(int i) { return at(i); }
};

#include "containers/sparse_vec.h"
#include "data/dataset_tpl.h"
#include "learning_tpl.h"

namespace po = boost::program_options;
po::options_description desc("Allowed options");
po::variables_map vm;

void process_options(int argc, char** argv);

template <typename vec>
void batchLearn()
{
  Dataset<vec> data;
  data.load(vm["data"].as<std::string>());
  std::vector<double> params;
  BatchLearningAlgorithm<vec> *p = NULL;

  if (vm["method"].as<std::string>() == "l1l2") {
    p = new L1LossL2Reg<vec>(data.dim());
    params.push_back(vm["alpha"].as<double>());
    params.push_back(vm["C"].as<double>());
    params.push_back(vm["iter"].as<int>());
  }
  else if (vm["method"].as<std::string>() == "l2l2") {
    p = new L2LossL2Reg<vec>(data.dim());
    params.push_back(vm["alpha"].as<double>());
    params.push_back(vm["C"].as<double>());
    params.push_back(vm["iter"].as<int>());
  }

  if (!p) {
    std::cout << "Unknown algorithm : " << vm["method"].as<std::string>() << "\n";
    return;
  }
  std::cout << "Algorithm : " << vm["method"].as<std::string>() << "\n";
//  std::cout << "start = \n"; p->print(); std::cout << "\n";
  p->setParameters(params);
  double error = p->update(data);
  std::cout << "result = \n"; p->print(); std::cout << "\n";
  for (int i = 0; i < data.n(); i++) {
    double pred = p->predict(data.x(i));
    std::cout << i << " y = " << data.y(i) << " pred = " << pred
              << " --> error = " << (data.y(i)*pred <= 0) << "\n";
  }
  std::cout << "error = " << error << "\n";
  delete p;
}

template <typename vec>
void generateData()
{
  double noise = vm["noise"].as<double>();
  int n = vm["n"].as<int>();
  int d = vm["d"].as<int>();
  Dataset<vec> data;
  data.generate(n, d, noise);
  data.save(vm["data"].as<std::string>());
}

void test_sparse();
void test_ublas();

int main(int argc, char** argv) {
//  test_sparse(); return 0;
//  test_ublas(); return 0;
  process_options(argc, argv);
  if (vm.count("help") || !vm.count("data")) {
    std::cout << desc << "\n";
    return 0;
  }
  std::cout << (vm.count("sparse") ? "sparse":"non-sparse") << "\n";
  if (vm.count("gen")) {
    if (vm.count("sparse"))
      generateData <arma::sparse_vec> ();
    else
      generateData <ArmaVec> ();
    return 0;
  }

  boost::posix_time::ptime time_start(boost::posix_time::microsec_clock::local_time());
  if (vm.count("sparse"))
    batchLearn <arma::sparse_vec> ();
  else
    batchLearn <ArmaVec> ();
  boost::posix_time::ptime time_end(boost::posix_time::microsec_clock::local_time());
  boost::posix_time::time_duration duration(time_end - time_start);
  std::cerr << "Duration = " << duration << "\n";
  return 0;
}

void process_options(int argc, char** argv)
{
  desc.add_options()
    ("help", "produce this usage")
    ("data", po::value<std::string>(), "file contains training samples")
    ("sparse", "use sparse vector representation")
    ("gen", "generate random data")
    ("noise", po::value<double>()->default_value(0.2), "learning rate")
    ("n", po::value<int>()->default_value(10), "number of points")
    ("d", po::value<int>()->default_value(2), "input dimension")
    ("iter", po::value<int>()->default_value(10), "maximum number of iterations")
    ("alpha", po::value<double>()->default_value(1.0), "learning rate")
    ("C", po::value<double>()->default_value(100.0), "regularized parameter")
    ("method", po::value<std::string>()->default_value("l1l2"), "learning algorithm: l1l2")
    ;
  po::store(po::parse_command_line(argc, argv, desc), vm);
  po::notify(vm);
}

typedef arma::sparse_vec vec;
//typedef arma::vec vec;

void test_sparse()
{
  vec a(10), b(10), c(10);
  a.zeros(); b.zeros(); c.zeros();
  a.ref(1) = 1; std::cout << "a = " << a << "\n";
  a.ref(1) = 2; std::cout << "a = " << a << "\n";
  //std::cout << "a.nnz = " << a.nnz() << "\n";
  std::cout << "a.dim = " << a.n_elem << "\n";
  b.randn(); std::cout << "b = " << b << "\n";
  c.randu(); std::cout << "c = " << c << "\n";
  b.zeros(); std::cout << "b = " << b << "\n";
  c.ones(); std::cout << "c = " << c << "\n";
  std::cout << "b/2 = " << b/2 << "\n";
  std::cout << "c/4 = " << c/4 << "\n";
  std::cout << "a-c = " << a-c << "\n";
  std::cout << "a+=c = " << (a+=c) << "\n";
  std::cout << "b-c = " << (b-c) << "\n";
  std::cout << "c+=b = " << (c+=b) << "\n";
  std::cout << "b[9] = " << b.get(0) << "\n";
  std::cout << "c[3] = " << c.get(3) << "\n";
  std::cout << "a[1] = " << a.get(1) << "\n";
  std::cout << "norm(a,1) = " << arma::norm(a, 1) << "\n";
  std::cout << "norm(b,2) = " << arma::norm(b, 2) << "\n";
  std::cout << "norm(c,inf) = " << arma::norm(c, "inf") << "\n";
  std::cout << "dot(c,b) = " << arma::dot(c, b) << "\n";
  std::cout << "dot(a,c) = " << arma::dot(a, c) << "\n";

  ::save(std::cout, a);
  std::cout << "\ninput b = "; ::load(std::cin, b);
  std::cout << "b = " << b << "\n";
}

#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/io.hpp>
void test_ublas()
{
  using namespace boost::numeric::ublas;
  for (int i = 0; i < 3; ++ i) {
      unit_vector<double> v (3, i);
      std::cout << v << std::endl;
  }
}
